Emberveil sourcegame/2006Scape Client/src/main/java/EmberveilWorldMap.java
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
/** Offline reference atlas. All window state belongs to the AWT event thread. */
public final class EmberveilWorldMap {
private static JDialog window;
// Published atomically by the game loop; Swing never reads mutable game state.
private static volatile Position position;
static final class Position {
final int x, y, floor;
Position(int x, int y, int floor) { this.x=x; this.y=y; this.floor=floor; }
}
static void updatePosition(boolean loggedIn, int x, int y, int floor) {
position = loggedIn ? new Position(x,y,floor) : null;
}
// This raster has two pixels per tile, west edge 2112 and north edge 4032.
// Half-tile offsets place the marker at the centre of the tile.
static Point mapPoint(Position p, BufferedImage image) {
if(p==null || p.floor<0 || p.floor>3) return null;
int x=(p.x-2112)*2+1, y=(4032-p.y)*2-1;
if(x<0 || y<0 || x>=image.getWidth() || y>=image.getHeight()) return null;
if((image.getRGB(x,y)&0xffffff)==0) return null;
return new Point(x,y);
}
public static void open(Frame owner) {
EventQueue.invokeLater(() -> {
if (window != null && window.isDisplayable()) {
window.setVisible(true);
window.toFront();
return;
}
try {
MapCanvas map = new MapCanvas(loadImage());
JDialog dialog = new JDialog(owner, "Emberveil | World Map", false);
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEFT));
controls.setBackground(new Color(48, 40, 30));
for (double scale : new double[] {0.25, 0.5, 0.75, 1, 1.5, 2}) {
JButton button = new JButton((int) (scale * 100) + "%");
button.addActionListener(e -> map.zoom(scale, map.getWidth()/2, map.getHeight()/2));
controls.add(button);
}
JButton fit = new JButton("Overview");
fit.addActionListener(e -> map.fit());
controls.add(fit);
JButton find = new JButton("Find me");
find.addActionListener(e -> map.findMe());
controls.add(find);
JLabel location = new JLabel();
location.setForeground(new Color(255, 220, 95));
javax.swing.Timer tracking = new javax.swing.Timer(200, e -> {
Position p=position;
boolean available=mapPoint(p,map.image)!=null;
find.setEnabled(available);
location.setText(p==null ? " Log in to show your position" : available
? " Yellow dot: you are here ("+p.x+", "+p.y+")"+(p.floor>0 ? " - upper floor, surface position" : "")
: " Your location is underground or outside this surface map");
map.repaint();
});
find.setEnabled(mapPoint(position,map.image)!=null);
location.setText(" Yellow dot: you are here | Find me centres on your position");
dialog.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) { tracking.stop(); }
});
tracking.start();
JButton close = new JButton("Close");
close.addActionListener(e -> dialog.dispose());
controls.add(close);
JLabel help = new JLabel(" Drag to explore | Mouse wheel to zoom | Esc to close");
help.setForeground(new Color(235, 218, 175));
JPanel header = new JPanel(new BorderLayout());
header.setBackground(controls.getBackground());
header.add(controls, BorderLayout.CENTER);
JPanel hints = new JPanel(new GridLayout(2,1));
hints.setBackground(controls.getBackground());
hints.add(help);
hints.add(location);
header.add(hints, BorderLayout.SOUTH);
JLabel note = new JLabel("<html>July 2005 surface reference - later areas and dungeons are not shown.<br>Map artwork: Jagex / RuneScape Wiki.</html>");
note.setBorder(BorderFactory.createEmptyBorder(6, 4, 6, 4));
dialog.add(header, BorderLayout.NORTH);
dialog.add(map, BorderLayout.CENTER);
dialog.add(note, BorderLayout.SOUTH);
dialog.getRootPane().registerKeyboardAction(e -> dialog.dispose(),
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
dialog.setSize(Math.min(1100, screen.width - 60), Math.min(800, screen.height - 80));
dialog.setMinimumSize(new Dimension(Math.min(720, screen.width-60), Math.min(480, screen.height-80)));
dialog.setLocationRelativeTo(owner);
window = dialog;
dialog.setVisible(true);
map.fit();
} catch (IOException | RuntimeException ex) {
JOptionPane.showMessageDialog(owner, "The bundled world map could not be loaded.\n" + ex.getMessage(),
"Emberveil World Map", JOptionPane.ERROR_MESSAGE);
}
});
}
static BufferedImage loadImage() throws IOException {
java.net.URL resource = EmberveilWorldMap.class.getResource("/emberveil-worldmap.png");
if (resource == null) throw new IOException("Missing emberveil-worldmap.png");
BufferedImage result = ImageIO.read(resource);
if (result == null) throw new IOException("Unreadable map image");
return result;
}
static final class MapCanvas extends JPanel {
final BufferedImage image;
double scale = 0.25, offsetX, offsetY;
private Point drag;
MapCanvas(BufferedImage image) {
this.image = image;
setBackground(Color.BLACK);
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
MouseAdapter mouse = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) drag = e.getPoint();
}
public void mouseReleased(MouseEvent e) { drag = null; }
public void mouseDragged(MouseEvent e) {
if (drag == null) return;
offsetX += e.getX() - drag.x;
offsetY += e.getY() - drag.y;
drag = e.getPoint();
constrain();
repaint();
}
public void mouseWheelMoved(MouseWheelEvent e) {
zoom(scale * Math.pow(1.15, -e.getWheelRotation()), e.getX(), e.getY());
}
};
addMouseListener(mouse);
addMouseMotionListener(mouse);
addMouseWheelListener(mouse);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) { constrain(); repaint(); }
});
}
void fit() {
scale = Math.min((double)getWidth()/image.getWidth(), (double)getHeight()/image.getHeight());
scale = Math.max(0.1, Math.min(2, scale));
offsetX = (getWidth() - image.getWidth()*scale)/2;
offsetY = (getHeight() - image.getHeight()*scale)/2;
repaint();
}
void findMe() {
Point point=mapPoint(position,image);
if(point==null) return;
scale=Math.max(scale,0.75);
offsetX=getWidth()/2.0-point.x*scale;
offsetY=getHeight()/2.0-point.y*scale;
constrain();
repaint();
}
void zoom(double value, int x, int y) {
double next = Math.max(0.1, Math.min(2, value));
offsetX = x - (x - offsetX) * next/scale;
offsetY = y - (y - offsetY) * next/scale;
scale = next;
constrain();
repaint();
}
private void constrain() {
double width = image.getWidth()*scale, height = image.getHeight()*scale;
offsetX = width <= getWidth() ? (getWidth()-width)/2 : Math.max(getWidth()-width, Math.min(0, offsetX));
offsetY = height <= getHeight() ? (getHeight()-height)/2 : Math.max(getHeight()-height, Math.min(0, offsetY));
}
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D g = (Graphics2D)graphics.create();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.translate(offsetX, offsetY);
g.scale(scale, scale);
g.drawImage(image, 0, 0, null);
g.dispose();
Point point=mapPoint(position,image);
if(point!=null) {
int x=(int)Math.round(offsetX+point.x*scale), y=(int)Math.round(offsetY+point.y*scale);
Graphics2D marker=(Graphics2D)graphics.create();
marker.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
marker.setColor(Color.BLACK); marker.fillOval(x-8,y-8,16,16);
marker.setColor(Color.WHITE); marker.fillOval(x-6,y-6,12,12);
marker.setColor(new Color(255,210,30)); marker.fillOval(x-4,y-4,8,8);
marker.dispose();
}
}
}
}